package mongodb

import helpers._
import org.bson.codecs.configuration.CodecRegistries._
import org.bson.codecs.configuration._
import org.mongodb.scala._
import org.mongodb.scala.bson.codecs.DEFAULT_CODEC_REGISTRY
import org.mongodb.scala.bson.conversions.Bson
import org.mongodb.scala.model.Projections._
import org.mongodb.scala.model.UpdateOptions

import scala.concurrent.Future
import scala.reflect.ClassTag
import scala.util.Failure

trait ExtendMongodbQL[T] extends ExtendMongodbContext[T] with NamedMongodbCollection with ExceptionDefining {
  this: PandoraMongodb with Logging ⇒

  import ExtendMongodbQL._

  type ClazzToBsonMap = T ⇒ MO

  protected def namespace: Option[String] = None

  protected def _Collection: MongoCollection[T]

  protected def codecRegistry: CodecRegistry

  protected def toBson: ClazzToBsonMap

  protected def registries(providers: CodecProvider*): CodecRegistry =
    fromRegistries(fromProviders(providers: _*), DEFAULT_CODEC_REGISTRY)

  protected def getCollection(codecRegistry: CodecRegistry): MongoCollection[MO] = //Fixme 类型问题
    namespace.fold {
      _Mongodb.db.withCodecRegistry(codecRegistry).getCollection(tableName)
    } { namespace ⇒
      _Mongodb.client
        .getDatabase(namespace)
        .withCodecRegistry(codecRegistry)
        .getCollection(tableName)
    }

  protected def MQL[T1](future: ⇒ Future[T1])(implicit ct: ClassTag[T]): Future[T1] =
    future.andThen {
      case Failure(ex) ⇒ Logger.debug("A general runtime exception raised in BSON processing", ex)
    }.recover {
      case ex: org.bson.BSONException ⇒ throw BsonException(Option(ex.getErrorCode), ex.getMessage)
    }

  def find(filter: Bson)(implicit ct: ClassTag[T]): Future[Seq[T]] = MQL {
    _Collection.find(filter).toFuture()
  }

  def findOne(filter: Bson)(implicit ct: ClassTag[T]): Future[Option[T]] = MQL {
    _Collection.find(filter).headOption()
  }

  def findOne(filter: Bson, projections: String*)(
      implicit ct: ClassTag[T]
  ): Future[Option[T]] = MQL {
    _Collection.find(filter).projection(include(projections: _*)).headOption()
  }

  def findAll(implicit ct: ClassTag[T]): Future[Seq[T]] = MQL {
    _Collection.find().toFuture()
  }

  def deleteOne(filter: Bson)(implicit ct: ClassTag[T]): Future[Boolean] = MQL {
    _Collection.deleteOne(filter).toFuture().map(_.wasAcknowledged)
  }

  def deleteMany(filter: Bson)(implicit ct: ClassTag[T]): Future[Boolean] = MQL {
    _Collection.deleteMany(filter).head().map(_.wasAcknowledged)
  }

  def save(one: T)(implicit ct: ClassTag[T]): Future[T] = MQL {
    _Collection.insertOne(one).head().map(_ ⇒ one)
  }

  def save(one: Traversable[T])(implicit ct: ClassTag[T]): Future[Boolean] = MQL {
    _Collection.insertMany(one.toSeq).head().map(_ ⇒ true)
  }

  def updateOne(filter: Bson, update: T)(implicit ct: ClassTag[T]): Future[T] = MQL {
    _Collection.updateOne(filter, MO($set → toBson(update))).head().map(_ ⇒ update)
  }

  def updateMay(filter: Bson, update: Bson)(implicit ct: ClassTag[T]): Future[Boolean] = MQL {
    _Collection.updateMany(filter, update).head().map(_.wasAcknowledged())
  }

  def upsertOne(filter: Bson, update: T)(implicit ct: ClassTag[T]): Future[T] = MQL {
    _Collection
      .updateOne(filter, MO($set → toBson(update)), UpdateOptions().upsert(true))
      .head()
      .map(_ ⇒ update)
  }

  //  def upsertMany(filter: Bson, update: Bson)(implicit ct: ClassTag[T]): Future[Boolean] = MQL {
  //    _Collection
  //      .updateMany(filter, update, UpdateOptions().upsert(true)).head().map(_.wasAcknowledged())
  //  }

  def isEmpty(implicit ct: ClassTag[T]): Future[Boolean] = MQL {
    _Collection.find().toFuture().map(_.isEmpty)
  }
}

object ExtendMongodbQL {

  case class BsonException(
      errorCode: Option[Int] = None,
      errorMessage: String
  ) extends BaseException(errorMessage)

}
